#include<iomanip>//用于输出结束
#include<time.h>//使函数具有随机性
#include<stdio.h>
#include<Windows.h>
#include<conio.h>

using namespace std;
HANDLE handle;//用于输出坐标
COORD crd;//用于设计输出坐标
short wall[80][40] = { 0 };//控制全局
const short type[19][16] = {
//Z型0-1后一个为其逆时针旋转后的图案（下同）
	{1,1,0,0,
	 0,1,1,0,
	 0,0,0,0,
	 0,0,0,0},

	{0,0,1,0,
	 0,1,1,0,
	 0,1,0,0,
	 0,0,0,0},
//s型2-3
	{0,1,1,0,
	 1,1,0,0,
	 0,0,0,0,
	 0,0,0,0},
	
	{0,1,0,0,
	 0,1,1,0,
	 0,0,1,0,
	 0,0,0,0},
//I型4-5
	{0,0,0,0,
	 1,1,1,1,
	 0,0,0,0,
	 0,0,0,0},
	
	{0,1,0,0,
	 0,1,0,0,
	 0,1,0,0,
	 0,1,0,0},
//方型6
	{1,1,0,0,
	 1,1,0,0,
	 0,0,0,0,
	 0,0,0,0},
//T型7-10
	{0,1,0,0,
	 1,1,1,0,
	 0,0,0,0,
	 0,0,0,0},
	
	{0,1,0,0,
	 1,1,0,0,
	 0,1,0,0,
	 0,0,0,0},
	
	{1,1,1,0,
	 0,1,0,0,
	 0,0,0,0,
	 0,0,0,0},
	
	{1,0,0,0,
	 1,1,0,0,
	 1,0,0,0,
	 0,0,0,0},
//L型11-14
	{0,0,1,0,
	 1,1,1,0,
	 0,0,0,0,
	 0,0,0,0},
	
	{0,1,1,0,
	 0,0,1,0,
	 0,0,1,0,
	 0,0,0,0},
	
	{1,1,1,0,
	 1,0,0,0,
	 0,0,0,0,
	 0,0,0,0},
	
	{0,1,0,0,
	 0,1,0,0,
	 0,1,1,0,
	 0,0,0,0},
//J型15-18
	{1,0,0,0,
	 1,1,1,0,
	 0,0,0,0,
	 0,0,0,0},
	
	{0,0,1,0,
	 0,0,1,0,
	 0,1,1,0,
	 0,0,0,0},
	
	{1,1,1,0,
	 0,0,1,0,
	 0,0,0,0,
	 0,0,0,0},
	
	{0,1,1,0,
	 0,1,0,0,
	 0,1,0,0,
	 0,0,0,0}
};
//设置输出坐标
void set_pos(short x, short y)
{
	crd = { x,y };//设置坐标
	SetConsoleCursorPosition(handle, crd);//句柄要先使用GetStdHandle函数
}
//前景色，背景色
void SetColor(short front, short back)
{
	SetConsoleTextAttribute(handle, front + back * 0x10);//设置颜色
}
//隐藏光标
void hide_cursor()
{
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(handle, &cursor_info);
	cursor_info.bVisible = false;
	SetConsoleCursorInfo(handle, &cursor_info);
}
//改变wall中边框的值
void init_mywindow()
{
	for (int j = 0; j < 40; j++)
		for (int i = 0; i < 80; i++)//初始化使边框设为2其他均为0
		{
			if ((i > 3 && i < 56 && j < 37) || j > 38 || i > 59)wall[i][j] = 0;
			else wall[i][j] = 2;
		}
	for (int i = 0; i < 40; i++)
		for (int j = 0; j < 80; j += 2)
		{
			if (wall[j][i] == 0)//使0处为白色
			{
				SetColor(2, 7);//设置颜色为白色
				printf("  ");
			}
			if (wall[j][i] == 2)//使2处为黑色
			{
				SetColor(7, 0);//设置颜色为黑色
				printf("  ");
			}
		}
}

struct moved
{
	//旋转后指向的方块
	short zhuan(short n);

	//擦除移动前的方块
	void rub(short x, short y);
	//显示移动后的方块
	void smear(short x, short y, short yanse, short shape);//传递shape是因为可能为旋转后的方块

	//判断是否能下降
	bool ifdown(short x, short y);
	//执行下降操作
	void down(short& x, short& y, short yanse, short shape);

	//判断能否右移
	bool ifright(short x, short y);
	//执行右移操作
	void right(short& x, short& y, short yanse, short shape);

	//判断能否左移
	bool ifleft(short x, short y);
	//执行左移操作
	void left(short& x, short& y, short yanse, short shape);

	//判断旋转
	bool ifrotate(short x, short y, short shape);
	//执行旋转
	void roatate(short x, short y, short& shape, short yanse);

	//判断是否需要执行消除操作
	bool ifremove(short y);
};
short moved::zhuan(short n)

{
	switch (n)//根据旋转前的方块形状对应的形状决定旋转后方块对应的形状
	{
	case 1:case 3:case 5:return n - 1; break;//如果是已旋转后的图案Z型S型I型再次旋转对应图案为其前方的图案
	case 6:return 6; break;//方型的不变
	case 10:case 14:case 18:return n - 3; break;//T型L型J型达到最后的图案向前调三
	default:return n + 1; break;//其余的全为后一个图案
	}
}

void moved::rub(short x, short y)
{
	SetColor(2, 7);//设置颜色为白色将原来的方块覆盖
	for (int i = 0; i < 16; i++)
	{
		if (wall[x + (i % 4) * 2][y + i / 4] == 1)//只将处于移动状态的方块擦除
		{
			wall[x + (i % 4) * 2][y + i / 4] = 0;
			set_pos(x + (i % 4) * 2, y + i / 4);
			printf("  ");//覆盖操作
		}
	}
}

void moved::smear(short x, short y, short color, short shape)
{
	for (int i = 0; i < 16; i++)//方块的形状所在区间为16
		if (type[shape][i] == 1)//找到需要输出方块的位置
			wall[x + (i % 4) * 2][y + i / 4] = type[shape][i];//对wall进行赋值
	SetColor(7, color);//设置输出的颜色
	for (int i = 0; i < 16; i++)
		if (wall[x + (i % 4) * 2][y + i / 4] == 1)//在wall为1的位置输出方块
		{
			set_pos(x + (i % 4) * 2, y + i / 4);//设置输出坐标
			printf("□");
		}
}


bool moved::ifdown(short x, short y)
{
	for (int i = 3; i >= 0; i--)
	{
		for (int j = 0; j < 8; j += 2)
			if (wall[x + j][y + i] == 1)//只有当有颜色的区域受阻才有可能返回false
			{
				if (wall[x + j][y + i + 1] == 2 || wall[x + j][y + i + 1] == 3)//如果下方有边界或者已落地的方块则返回false
					return  false;
			}
	}
	return true;
}

void moved::down(short& x, short& y, short yanse, short shape)
{
	rub(x, y);//擦除
	y++;//改变心输出方块的坐标
	smear(x, y, yanse, shape);//输出新的方块
}

bool moved::ifright(short x, short y)
{
	for (int j = 6; j >= 0; j -= 2)//从右向左判断
	{
		for (int i = 0; i < 4; i++)
			if (wall[x + j][y + i] == 1)//只有当有颜色的区域受阻才有可能返回false
			{
				if (wall[x + j + 2][y + i] == 2 || wall[x + j + 2][y + i] == 3)//加2为其右方的位置的数字如果为2是因为到达边界为3是因为碰到已落地的方块
					return false;
			}
	}
	return true;
}

void moved::right(short& x, short& y, short yanse, short shape)
{
	rub(x, y);//擦除
	x += 2;//改变心输出方块的坐标
	smear(x, y, yanse, shape);//输出新的方块
}

bool moved::ifleft(short x, short y)
{
	for (int j = 0; j < 8; j += 2)//从左向右判断
	{
		for (int i = 0; i < 4; i++)
			if (wall[x + j][y + i] == 1)//只有当有颜色的区域受阻才有可能返回false
			{
				if (wall[x + j - 1][y + i] == 2 || wall[x + j - 2][y + i] == 3)//减1为其左方的位置的数字如果为2是因为到达边界  减2为3是因为碰到已落地的方块
					return  false;
			}
	}
	return true;
}
void moved::left(short& x, short& y, short yanse, short shape)
{
	rub(x, y);//擦除
	x -= 2;//改变心输出方块的坐标
	smear(x, y, yanse, shape);//输出新的方块
}

bool moved::ifrotate(short x, short y, short shape)
{
	//定义方块旋转受阻位置
	const short zuai[19][16] = {
			{0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0},{1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0},//Z型0-1与上方对应保留其受阻的位置（下同）
			{0,0,0,0,0,0,1,0,1,1,1,0,0,0,0,0},{0,0,1,0,1,0,0,0,1,1,0,0,0,0,0,0},//S型2-3
			{1,1,1,1,0,0,0,0,0,1,1,1,0,1,1,1},{1,0,1,1,1,0,1,1,0,0,1,1,0,0,1,1},//I型4-5
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},//方型6 
			{1,0,1,0,0,0,0,0,1,1,0,0,0,0,0,0},{1,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0},{0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0},{0,1,0,0,0,0,1,0,0,1,1,0,0,0,0,0},//T型7-10
			{0,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0},{1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,1,0,0,1,1,1,0,0,0,0,0},{1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0},//L型11-14
			{0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0},{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0},{1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0}//J型15-18
	};
	for (int i = 0; i < 16; i++)
		if (zuai[shape][i] == 1 && wall[x + (i % 4) * 2][y + i / 4] != 0)return false;//如果阻碍方块旋转的位置有方块则返回false
	return true;//否则返回true
}
void moved::roatate(short x, short y, short& shape, short yanse)
{
	shape = zhuan(shape);
	rub(x, y);
	for (int i = 0; i < 16; i++)//方块的形状所在区间为16
		if (type[shape][i] == 1)//找到需要输出方块的位置
			wall[x + (i % 4) * 2][y + i / 4] = type[shape][i];//对wall进行赋值
	SetColor(7, yanse);//设置输出的颜色
	for (int i = 0; i < 16; i++)
		if (wall[x + (i % 4) * 2][y + i / 4] == 1)//在wall为1的位置输出方块
		{
			set_pos(x + (i % 4) * 2, y + i / 4);//设置输出坐标
			printf("□");
		}
}
bool moved::ifremove(short y)
{
	int j = 0;//用于计算一排共有多少个方块
	for (int i = 4; i < 56; i += 2)
		if (wall[i][y] == 3)j++;
	if (j == 26)//判断这一排的格子是否为满
		return true;
	else return false;
}

struct App
{
	//处理暂停操作
	int source = 0;//计分
	void remove_color(short y);
	//在上方为3的位置输出方块
	void flash_wall(short y);
	//返回当前的分数
	int Getsource() { return source; };
	//增加分数
	void Add(short linescore)
	{
		source += linescore * linescore * 100;
	}
	//根据方块颜色返回一种形状
	short color_to_shape(short& f);
	//显示下一个方块的形状
	void show_next(short yanse);
	//将落地的方块归为3
	void flash(short x, short y);
	//去除方块上方方块的颜色
	void remove(short x, short y);
	//菜单
	bool mune();
	//处理暂停操作
	bool PAUSE();
};
void App::remove_color(short y)
{
	for (int j = y; j >= 0; j--)
		for (int i = 3; i < 56; i++)
			if (wall[i][j] == 3)
			{
				SetColor(2, 7);//设置为白色
				set_pos(i, j);
				printf("  ");//覆盖上方的格子
			}
}

void App::flash_wall(short y)
{
	for (int j = y; j >= 0; j--)
		for (int i = 3; i < 56; i++)
			if (wall[i][j] == 3)
			{
				SetColor(7, 8);//设置为灰色
				set_pos(i, j);
				printf("□");//在为3的地方输出新格子
			}
}


short App::color_to_shape(short& f)
{
	switch (f)//根据颜色返回出方块的形状
	{
	case 1:return 0;//如果为深蓝色则为Z型方块
	case 2:return 2;//如果为绿色则为S型方块
	case 3:return 4;//如果为浅蓝色则为I型方块
	case 4:return 6;//如果为红色则为方块
	case 5:return 7;//如果为紫色则为T型
	case 6:return 11;//如果为橙色则为L型
	default:f = 8; return 15;//其他均为J型f==7时为白色所以跳过
	}
}

void App::show_next(short yanse)
{
	short one_ = color_to_shape(yanse);
	for (int j = 0; j < 16; j++)
	{
		SetColor(2, 7);
		set_pos(64 + (j % 4) * 2, j / 4);
		printf("  ");
		if (type[one_][j] == 1)
		{
			SetColor(7, yanse);
			set_pos(64 + (j % 4) * 2, j / 4);
			printf("□");
		}
	}
	SetColor(0, 5);
	set_pos(60, 5);
	printf("下一个在上面哦！");
}

void App::flash(short x, short y)
{
	for (int i = 0; i < 16; i++)
	{
		if (wall[x + (i % 4) * 2][y + i / 4] == 1)
		{
			SetColor(7, 8);
			wall[x + (i % 4) * 2][y + i / 4] = 3;//使落地的方块全归为3便于消除时再次上色
			set_pos(x + (i % 4) * 2, y + i / 4);
			printf("□");//输出新颜色的方块
		}
	}
}

void App::remove(short x, short y)
{
	remove_color(y);//先去色但不改变其数字
	for (int d = y; d > 0; d--)
		for (int i = 4; i < 52; i += 2)
			wall[i][d] = wall[i][d - 1];//将上方的值向下移					
	flash_wall(y);//找到为3的位置输出方块
}

bool App::mune()
{
	char c = 0;
	bool choice = false;//保存用户选择
	set_pos(17, 12);//┌ ─ ┐└ ┘│改变输出位置
	printf("┌────────────────┐");
	set_pos(17, 13);
	printf("│ 俄 罗 斯 方 块 │");
	set_pos(17, 14);
	printf("└────────────────┘");
	SetColor(2, 0);
	set_pos(15, 23);//改变输出位置
	printf("提示：");
	set_pos(15, 24);//改变输出位置
	printf("1.上键为逆时针旋转；");
	set_pos(15, 25);//改变输出位置
	printf("2.下键加速；");
	set_pos(15, 26);//改变输出位置
	printf("3.每次消的行数越多，得分越高");
	set_pos(15, 27);//改变输出位置
	printf("4.游戏时间禁止学习！");
	while (1)
	{
		if (c == 77 || c == 75)choice = !choice;//判断c==77为右键c==75为左键改变choice的值来确定该改变输出哪个文字的亮度
		if (c == VK_RETURN)break;//表示用户做出了选择退出循环
		if (!choice)//如果是0则开始游戏背景色为白色
			SetColor(2, 7);
		set_pos(17, 20);//改变输出位置
		printf("开始游戏");
		SetColor(2, 0);//设置输出颜色为黑色背景绿色字体
		if (choice)//如果是1则退出背景色为白色
			SetColor(2, 7);
		set_pos(31, 20);//改变输出位置
		printf("退出");
		SetColor(2, 0);//设置下一次输出的背景为黑色文字为绿色
		c = _getch();//获得键盘输入
	}
	return choice;//返回用户的选择
}

bool App::PAUSE()
{
	bool choice = false;
	char f = 0;
	SetColor(2, 0);//设置输出颜色为黑色背景绿色字体
	set_pos(61, 23);
	printf("游戏以暂停");//输出提示游戏已暂停
	while (1)
	{
		if (f == 72 || f == 80)choice = !choice;
		if (f == VK_RETURN)break;//表示用户做出了选择退出循环
		SetColor(2, 0);//设置输出颜色为黑色背景绿色字体
		set_pos(61, 26);
		if (!choice)//如果是0则开始游戏背景色为白色
			SetColor(2, 7);
		printf("退出");//输出下一步提示
		SetColor(2, 0);//设置输出颜色为黑色背景绿色字体
		set_pos(61, 28);
		if (choice)//如果是0则开始游戏背景色为白色
			SetColor(2, 7);
		printf("继续");//输出提示
		f = _getch();
	}
	set_pos(39, 39);
	if (choice)//如果按下空格选择退出
	{
		if (GetAsyncKeyState(VK_UP) && 0x26);//接收刚刚选择时的上键但不执行操作，防止被移动时接收
		return true;
	}
	else
	{
		if (GetAsyncKeyState(VK_UP) && 0x26);//接收刚刚选择时的上键但不执行操作，防止被移动时接收
		SetColor(2, 7);
		set_pos(61, 23);
		printf("          ");//覆盖刚刚输出的“游戏已暂停”
		set_pos(61, 26);
		printf("                  ");//覆盖输出的“空格键+enter退出”
		set_pos(61, 28);
		printf("                   ");
	}
	return false;
}
void over_window(App a)
{
	SetColor(0, 7);
	int fen = a.Getsource();
	system("cls");
	printf("                  _oo0oo_     ___________________\n");
	printf("                 o8888888o   |你的得分是：%7d| \n", fen);
	printf("                 88\" . \"88  /|");
	if (fen < 500)printf("有点低了，在加油啊!| \n");
	else if (fen < 1400)printf("相信你还可以更强！ |\n");
	else printf("大佬大佬！-_-!强啊 |\n");
	printf("                 (| -_- |) / |___________________|\n");
	printf("                 0\\  =  /0\n");
	printf("              ____/'---'\\____\n");
	printf("            .'  \\\\|     |//  '.\n");
	printf("           / \\\\|||  :  |||//   \\\n");
	printf("          /  _||||| -:- |||||-  \\\n");
	printf("          |   | \\\\\\  -  /// |   |\n");
	printf("          | \\_|  ''\\---/''  |   |\n");
	printf("          \\  .-\\__  '_' ____/-. /\n");
	printf("        ___·. .' /--`--\\  '. .___\n");
	printf("     ·\"\" ',< '.__\\_<|>_/__.' >'\"\"·\n");
	printf("     | | ： ‘-\\'.:'\\_/';.'/ -' ; | |\n");
	printf("     \\  \\ '-.   \\_ _\\ /_ _/  .-' /  /\n");
	printf(" ====='-.___'-.___\\_____/__.-'___,-'=====\n");
	system("pause");
}
int main()
{
	handle = GetStdHandle(STD_OUTPUT_HANDLE);
	srand(unsigned(time(0)));
	hide_cursor();
	system("mode con cols=60 lines=40");
	App app;//生成一个游戏记录
	if (app.mune())
	{
		return 0;
	}
	system("cls");
	system("mode con cols=80 lines=41");//改变界面大小
	init_mywindow();//初始化界面使边缘数字设为2
	set_pos(61, 18);
	printf("分数为:%d", app.Getsource());
	set_pos(61, 21);
	printf("空格键暂停");
	short next_color = rand() % 7 + 1;//随机产生一种方块的颜色
	while (1)
	{
		short x = 24, y = 0;//x/y分别为方块左上角的坐标，对其进行标记
		short now_color = next_color;//保存随机产生的方块颜色计为当前方块的颜色
		next_color = rand() % 7 + 1;//再一次随机产生一种方块的颜色
		short shape = app.color_to_shape(now_color);//根据颜色获取对应的方块
		app.show_next(next_color);//显示下一个方块
		{
			for (int i = 0; i < 16; i++)//方块的形状所在区间为16
				if (type[shape][i] == 1)//找到需要输出方块的位置
					wall[x + (i % 4) * 2][y + i / 4] = type[shape][i];//对wall进行赋值
			SetColor(7, now_color);//设置输出的颜色
			for (int i = 0; i < 16; i++)
				if (wall[x + (i % 4) * 2][y + i / 4] == 1)//在wall为1的位置输出方块
				{
					set_pos(x + (i % 4) * 2, y + i / 4);//设置输出坐标
					printf("□");
				}
			set_pos(79, 38);//使光标移到一边
		}
		moved judge;//生成判断移动的对象执行判断操作
		bool isrun = true;
		do
		{
			for (short i = 0; i < 2; i++)//使一次下落 可以 进行两次控制
			{
				if (judge.ifright(x, y) && GetAsyncKeyState(VK_RIGHT) && 0x27)//判断是否右移
					judge.right(x, y, now_color, shape);
				else if (judge.ifleft(x, y) && GetAsyncKeyState(VK_LEFT) && 0x25)//判断是否左移
					judge.left(x, y, now_color, shape);
				else if (judge.ifrotate(x, y, shape) && GetAsyncKeyState(VK_UP) && 0x26)//判断是否旋转
					judge.roatate(x, y, shape, now_color);
				if (GetAsyncKeyState(VK_DOWN) && 0x28)//判断是否按了下键如果是，则减少睡眠时间
					Sleep(50);
				else Sleep(250);
				if (GetAsyncKeyState(VK_SPACE) && 0x20)//如果按下空格暂停游戏
					if (app.PAUSE())
						isrun = false;
			}
			if (isrun && judge.ifdown(x, y))//判断是否可以下移
				judge.down(x, y, now_color, shape);
			else break;
		} while (isrun);
		if (!isrun || (x == 24 && y == 0))break;//判断是按下空格退出还是因为无法移动
		app.flash(x, y);//将落地的方块归为3
		short linesource = 0;//计算的消除的行数
		for (short i = 0; i < 4; i++)//寻找有没有消除的行
		{
			if (judge.ifremove(y + i))//如果满足消除
			{
				app.remove(x, y + i);//执行移除
				linesource++;
			}
		}
		app.Add(linesource);//增加分数
		SetColor(2, 7);
		set_pos(61, 18);
		printf("分数为:%d", app.Getsource());
	}//对wall进行赋值并输出当前方块的形状
	over_window(app);
	return 0;
}